package com.xhj.mybatis.binding;

import com.xhj.mybatis.mapping.MappedStatement;
import com.xhj.mybatis.mapping.SqlCommandType;
import com.xhj.mybatis.reflection.MetaObject;
import com.xhj.mybatis.reflection.ParamNameResolver;
import com.xhj.mybatis.reflection.TypeParameterResolver;
import com.xhj.mybatis.session.Configuration;
import com.xhj.mybatis.session.RowBounds;
import com.xhj.mybatis.session.SqlSession;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;

/**
 * MapperMethod
 *
 * @author XJks
 * @description MapperMethod 类用于封装 Mapper 接口中每个方法的执行逻辑。它负责将 Mapper 方法的参数转换为 SQL 参数，并根据方法的返回类型决定如何执行 SQL 语句和处理结果。
 */
public class MapperMethod {

    /**
     * SqlCommand 封装了 SQL 语句的名称和类型（如 INSERT、SELECT 等）
     */
    private final SqlCommand command;

    /**
     * MethodSignature 封装了方法的签名信息，如返回类型、参数类型等
     */
    private final MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {
        this.command = new SqlCommand(configuration, mapperInterface, method);
        this.method = new MethodSignature(configuration, mapperInterface, method);
    }

    /**
     * MapperProxy 调用该方法，执行具体的 SQL 语句
     */
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result = null;
        switch (command.getType()) {
            case INSERT: {
                // 插入操作，参数转换后执行 insert
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case DELETE: {
                // 删除操作，参数转换后执行 delete
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case UPDATE: {
                // 更新操作，参数转换后执行 update
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case SELECT: {
                // todo:源码中还存在其他分支处理逻辑，这里简化处理，只处理常见的查询场景，只根据返回结果类型是否为集合或单个对象进行区分。
                // 查询操作，根据返回类型选择不同的处理方式
                if (method.returnsMany()) {
                    // 返回集合或数组
                    result = executeForMany(sqlSession, args);
                } else {
                    // 返回单个对象
                    Object param = method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(command.getName(), param);
                }
                break;
            }
            default:
                throw new RuntimeException("Unknown execution method for: " + command.getName());
        }
        return result;
    }

    /**
     * 根据方法的返回类型，将受影响的行数转换为对应的返回值类型
     *
     * @param rowCount 受影响的行数（如插入、更新、删除操作返回的行数）
     * @return 转换后的结果，类型与 Mapper 方法的返回类型一致
     */
    private Object rowCountResult(int rowCount) {
        // 最终要返回的结果对象
        final Object result;
        if (method.returnsVoid()) {
            // 如果方法返回类型是 void，则直接返回 null
            result = null;
        } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
            // 如果方法返回类型是 Integer 或 int，则返回受影响的行数（rowCount）
            result = rowCount;
        } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
            // 如果方法返回类型是 Long 或 long，则将 rowCount 转为 long 类型返回
            result = (long) rowCount;
        } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
            // 如果方法返回类型是 Boolean 或 boolean，则返回 rowCount > 0（即是否有行被影响）
            result = rowCount > 0;
        } else {
            // 如果返回类型不是上述几种，抛出异常，提示不支持该返回类型
            throw new RuntimeException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
        }
        // 返回最终结果
        return result;
    }

    /**
     * 执行查询操作，返回集合或数组类型的结果
     *
     * @param sqlSession MyBatis 的 SqlSession 对象，用于执行数据库操作
     * @param args       Mapper 方法的参数数组
     * @return 查询结果，类型为集合或数组
     */
    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        // 定义一个用于存放查询结果的 List
        List<E> result;
        // 将方法参数转换为 SQL 语句需要的参数对象
        Object param = method.convertArgsToSqlCommandParam(args);
        // 判断方法参数中是否包含 RowBounds（用于分页）
        if (method.hasRowBounds()) {
            // 如果有 RowBounds，从参数中提取出来
            RowBounds rowBounds = method.extractRowBounds(args);
            // 执行带分页的查询，返回结果列表
            result = sqlSession.selectList(command.getName(), param, rowBounds);
        } else {
            // 没有分页参数，直接查询，返回结果列表
            result = sqlSession.selectList(command.getName(), param);
        }
        // 判断方法的返回类型是否与 List 类型兼容
        if (!method.getReturnType().isAssignableFrom(result.getClass())) {
            // 如果返回类型是数组，则将 List 转换为数组返回
            if (method.getReturnType().isArray()) {
                return convertToArray(result);
            } else {
                // 如果返回类型是其他集合类型，则转换为指定集合类型返回
                return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
            }
        }
        // 如果返回类型本身就是 List，直接返回查询结果
        return result;
    }

    /**
     * 将 List 转换为指定集合类型
     *
     * @param config MyBatis 配置对象，用于获取对象工厂
     * @param list   查询结果的 List
     * @return 转换后的集合对象，类型与 Mapper 方法的返回类型一致
     */
    private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
        // 使用 MyBatis 的对象工厂，根据方法的返回类型创建一个集合实例（如 ArrayList、HashSet 等）
        Object collection = config.getObjectFactory().create(method.getReturnType());
        // 用 MetaObject 包装集合对象，方便批量操作
        MetaObject metaObject = config.newMetaObject(collection);
        // 将查询结果 list 中的所有元素添加到集合对象中
        metaObject.addAll(list);
        // 返回最终的集合对象
        return collection;
    }

    /**
     * 将 List 转换为数组
     *
     * @param list 查询结果的 List
     * @return 转换后的数组对象，类型与 Mapper 方法的返回类型一致
     */
    private <E> Object convertToArray(List<E> list) {
        // 获取返回类型的数组元素类型（如 User[] 的元素类型是 User）
        Class<?> arrayComponentType = method.getReturnType().getComponentType();
        // 创建一个指定类型和长度的数组对象
        Object array = Array.newInstance(arrayComponentType, list.size());
        // 如果数组元素类型是基本类型（如 int、long 等）
        if (arrayComponentType.isPrimitive()) {
            // 逐个将 list 中的元素设置到数组中
            for (int i = 0; i < list.size(); i++) {
                Array.set(array, i, list.get(i));
            }
            // 返回最终的基本类型数组
            return array;
        } else {
            // 如果数组元素类型是对象类型，直接用 List 的 toArray 方法转换
            return list.toArray((E[]) array);
        }
    }

    /**
     * SQL 指令
     */
    public static class SqlCommand {
        /**
         * sql语句的唯一标识，格式:namespace.id，与 Mapper 接口中的方法一一对应
         */
        private final String name;

        /**
         * sql语句的类型，insert、delete、update、select
         */
        private final SqlCommandType type;

        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            String statementId = mapperInterface.getName() + "." + method.getName();
            MappedStatement ms = configuration.getMappedStatement(statementId);
            name = ms.getId();
            type = ms.getSqlCommandType();
        }

        public String getName() {
            return name;
        }

        public SqlCommandType getType() {
            return type;
        }
    }

    /**
     * 参数 Map，支持参数名校验
     */
    public static class ParamMap<V> extends HashMap<String, V> {

        private static final long serialVersionUID = 1L;

        @Override
        public V get(Object key) {
            if (!super.containsKey(key)) {
                throw new RuntimeException("Parameter '" + key + "' not found. Available parameters are " + keySet());
            }
            return super.get(key);
        }

    }

    /**
     * MethodSignature 封装 Mapper 方法的签名信息
     */
    public static class MethodSignature {

        /**
         * 方法是否返回集合或数组
         */
        private final boolean returnsMany;

        /**
         * 方法是否返回 void
         */
        private final boolean returnsVoid;

        /**
         * 方法的返回类型
         */
        private final Class<?> returnType;

        /**
         * XxxMapper.class 对应方法参数列表中 RowBounds 类型参数的位置索引
         */
        private final Integer rowBoundsIndex;

        /**
         * 参数名解析器
         */
        private final ParamNameResolver paramNameResolver;

        /**
         * 构造方法，解析方法签名
         */
        public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
            // 解析方法的返回类型，支持泛型
            Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
            if (resolvedReturnType instanceof Class<?>) {
                // 如果返回类型是普通类，直接赋值
                this.returnType = (Class<?>) resolvedReturnType;
            } else if (resolvedReturnType instanceof ParameterizedType) {
                // 如果返回类型是带泛型的类型（如 List<User>），取其原始类型（如 List）
                this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
            } else {
                // 其他情况，使用反射获取的返回类型
                this.returnType = method.getReturnType();
            }
            // 判断返回类型是否为 void
            this.returnsVoid = void.class.equals(this.returnType);
            // 判断返回类型是否为集合或数组
            this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
            // 获取 RowBounds 参数在参数列表中的索引（如果有）
            this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
            // 创建参数名解析器，用于将参数数组转换为带参数名的 Map
            this.paramNameResolver = new ParamNameResolver(configuration, method);
        }

        /**
         * 将方法参数转换为 SQL 参数
         */
        public Object convertArgsToSqlCommandParam(Object[] args) {
            return paramNameResolver.getNamedParams(args);
        }

        /**
         * 是否有 RowBounds 参数
         */
        public boolean hasRowBounds() {
            return rowBoundsIndex != null;
        }

        /**
         * 获取 RowBounds 参数
         */
        public RowBounds extractRowBounds(Object[] args) {
            return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
        }

        /**
         * 获取返回类型
         */
        public Class<?> getReturnType() {
            return returnType;
        }

        /**
         * 是否返回集合或数组
         */
        public boolean returnsMany() {
            return returnsMany;
        }

        /**
         * 是否返回 void
         */
        public boolean returnsVoid() {
            return returnsVoid;
        }

        /**
         * 获取方法参数列表中指定类型参数的唯一索引（如 RowBounds、ResultHandler），如果有多个该类型参数，则抛出异常。
         *
         * @param method    方法对象
         * @param paramType 参数类型
         * @return 参数索引，如果没有找到则返回 null
         */
        private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
            // 用于记录找到的参数索引，初始为 null
            Integer index = null;
            // 获取方法的所有参数类型
            final Class<?>[] argTypes = method.getParameterTypes();
            // 遍历每个参数类型
            for (int i = 0; i < argTypes.length; i++) {
                // 判断当前参数类型是否为目标类型或其子类
                if (paramType.isAssignableFrom(argTypes[i])) {
                    if (index == null) {
                        // 如果还没有找到过该类型参数，记录当前参数的索引
                        index = i;
                    } else {
                        // 如果已经找到过一次，说明方法参数中有多个该类型参数，这是不允许的，直接抛出异常
                        throw new RuntimeException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
                    }
                }
            }
            // 返回找到的参数索引，如果没有则为 null
            return index;
        }

    }

}
